home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / bin / gnome-about < prev    next >
Encoding:
Text File  |  2009-05-05  |  36.6 KB  |  1,053 lines

  1. #!/usr/bin/python
  2. # coding=utf-8
  3.  
  4. '''
  5. gnome-about
  6.  
  7.  # Pretty About Dialog for the GNOME Desktop #
  8.  
  9. Copyright (C) 2007 Guillaume Seguin <guillaume@segu.in>
  10.  
  11. This program is free software; you can redistribute it and/or
  12. modify it under the terms of the GNU General Public License
  13. as published by the Free Software Foundation; either version 2
  14. of the License, or (at your option) any later version.
  15.  
  16. This program is distributed in the hope that it will be useful,
  17. but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. GNU General Public License for more details.
  20.  
  21. You should have received a copy of the GNU General Public License
  22. along with this program; if not, write to the Free Software
  23. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
  24.  
  25. Authors:
  26.     Guillaume Seguin <guillaume@segu.in>
  27.     Vincent Untz <vuntz@gnome.org> (get_language_names () helper function)
  28. '''
  29.  
  30. import pygtk
  31. pygtk.require ('2.0')
  32.  
  33. import gobject
  34. from gobject.option import OptionParser, make_option
  35. import gtk
  36.  
  37. import gnome
  38. import cairo
  39. from math import pi
  40.  
  41. import os, sys, random, time, gettext, locale
  42.  
  43. import xml.dom.minidom
  44.  
  45. DESCRIPTION_DELAY = 10000
  46. CONTRIBUTOR_DELAY = 2500
  47.  
  48. PACKAGE         = "gnome-desktop"
  49. VERSION         = "2.26.1"
  50. GETTEXT_PACKAGE = "gnome-desktop-2.0"
  51.  
  52. LOCALEDIR       = "/usr/share/locale"
  53. DATADIR         = "/usr/share/gnome-about"
  54. ICONDIR         = "/usr/share/pixmaps"
  55.  
  56. LOGO_FILE        = "gnome-64.png"
  57.  
  58. gettext.install (GETTEXT_PACKAGE, LOCALEDIR, unicode = True)
  59.  
  60. header_links = [
  61.     (_("About GNOME"), "http://www.gnome.org/about/"),
  62.     (_("News"), "http://news.gnome.org/"),
  63.     (_("GNOME Library"), "http://library.gnome.org/"),
  64.     (_("Friends of GNOME"), "http://www.gnome.org/friends/"),
  65.     (_("Contact"), "http://www.gnome.org/contact/"),
  66. ]
  67.  
  68. translated_contributors = [
  69.     _("The Mysterious GEGL"),
  70.     _("The Squeaky Rubber GNOME"),
  71.     _("Wanda The GNOME Fish")
  72. ]
  73.  
  74. default_link_color = gtk.gdk.Color (0, 0, 65535)
  75.  
  76. def locate_file (program, file):
  77.     '''Wrap gnome_program_locate_file to avoid ugly duplication'''
  78.     file = program.locate_file (gnome.FILE_DOMAIN_APP_DATADIR, file, True)
  79.     if not file:
  80.         return False
  81.     return file[0]
  82.  
  83. def cleanup_date (date):
  84.     '''Parse a date as found in gnome-version.xml and nicely format it'''
  85.     try:
  86.         # FIXME: we don't have g_locale_to_utf8 in python. See bug #530382
  87.         return unicode (time.strftime ("%x", time.strptime (date, "%Y-%m-%d")), locale.getpreferredencoding ()).encode ("utf-8")
  88.     except:
  89.         return ""
  90.  
  91. # Imported from GNOME's Sabayon
  92. # (sabayon/admin-tool/lockdown/disabledapplets.py)
  93. # There's no wrapper for g_get_language_names (). Ugly workaround:
  94. # Note that we don't handle locale alias...
  95. def get_language_names ():
  96.     if "LANGUAGE" in os.environ.keys () and os.environ["LANGUAGE"] != "":
  97.         env_lang = os.environ["LANGUAGE"].split ()
  98.     elif "LC_ALL" in os.environ.keys () and os.environ["LC_ALL"] != "":
  99.         env_lang = os.environ["LC_ALL"].split ()
  100.     elif "LC_MESSAGES" in os.environ.keys () and os.environ["LC_MESSAGES"] != "":
  101.         env_lang = os.environ["LC_MESSAGES"].split ()
  102.     elif "LANG" in os.environ.keys () and os.environ["LANG"] != "":
  103.         env_lang = os.environ["LANG"].split ()
  104.     else:
  105.         env_lang = []
  106.  
  107.     env_lang.reverse ()
  108.     languages = []
  109.  
  110.     for language in env_lang:
  111.         start_pos = 0
  112.         mask = 0
  113.         uscore_pos = language.find ("_")
  114.         if uscore_pos != -1:
  115.             start_pos = uscore_pos
  116.             mask += 1 << 2
  117.         dot_pos = language.find (".", start_pos)
  118.         if dot_pos != -1:
  119.             start_pos = dot_pos
  120.             mask += 1 << 0
  121.         at_pos = language.find ("@", start_pos)
  122.         if at_pos != -1:
  123.             start_pos = at_pos
  124.             mask += 1 << 1
  125.  
  126.         if uscore_pos != -1:
  127.             lang = language[:uscore_pos]
  128.         elif dot_pos != -1:
  129.             lang = language[:dot_pos]
  130.         elif at_pos != -1:
  131.             lang = language[:at_pos]
  132.         else:
  133.             lang = language
  134.  
  135.         if uscore_pos != -1:
  136.             if dot_pos != -1:
  137.                 territory = language[uscore_pos:dot_pos]
  138.             elif at_pos != -1:
  139.                 territory = language[uscore_pos:at_pos]
  140.             else:
  141.                 territory = language[uscore_pos:]
  142.         else:
  143.             territory = ""
  144.  
  145.         if dot_pos != -1:
  146.             if at_pos != -1:
  147.                 codeset = language[dot_pos:at_pos]
  148.             else:
  149.                 codeset = language[dot_pos:]
  150.         else:
  151.             codeset = ""
  152.  
  153.         if at_pos != -1:
  154.             modifier = language[at_pos:]
  155.         else:
  156.             modifier = ""
  157.  
  158.         for i in range (mask + 1):
  159.             if i & ~mask == 0:
  160.                 newlang = lang
  161.                 if (i & 1 << 2):
  162.                     newlang += territory
  163.                 if (i & 1 << 0):
  164.                     newlang += codeset
  165.                 if (i & 1 << 1):
  166.                     newlang += modifier
  167.                 languages.insert (0, newlang)
  168.  
  169.     return languages
  170.  
  171. class GettableList (list):
  172.     '''Dumb wrapper around Python list with a get () method to iterate \
  173. the list'''
  174.  
  175.     current = 0
  176.  
  177.     def get (self):
  178.         if not len (self):
  179.             return None
  180.         if self.current == -1:
  181.             item = None
  182.         else:
  183.             item = self[self.current]
  184.         self.current += 1
  185.         if self.current == len (self):
  186.             self.current = -1
  187.         else:
  188.             self.current = self.current % len (self)
  189.         return item
  190.  
  191. class GnomeContributors (GettableList):
  192.     '''Randomized contributors list'''
  193.  
  194.     program      = None
  195.     current      = 0
  196.  
  197.     def __init__ (self, program):
  198.         '''Initiate object and load contributors lists'''
  199.         super (GnomeContributors, self).__init__ ()
  200.         self.program = program
  201.         map (self.append, translated_contributors)
  202.         self.load_from_file ("contributors.list")
  203.         self.load_from_file ("foundation-members.list")
  204.         random.shuffle (self) # Randomize list...
  205.  
  206.     def load_from_file (self, file):
  207.         '''Load a list of contributors and validate it'''
  208.         def validate_contributor (contrib):
  209.             try:
  210.                 contrib.encode ("utf8")
  211.                 return len (contrib) > 0 and contrib[0] != "#"
  212.             except Exception, e:
  213.                 print e
  214.                 return False
  215.  
  216.         path = locate_file (self.program, file)
  217.  
  218.         if not path:
  219.             print '''Warning: "%s" file not found.''' % file
  220.             return
  221.  
  222.         f = open (path, "r")
  223.         try:
  224.             data = f.readlines ()
  225.         finally:
  226.             f.close ()
  227.  
  228.         '''Cleanup list'''
  229.         data = map (lambda s: s.rstrip (), data)
  230.  
  231.         '''Check that the file begins with the correct header'''
  232.         if not data or data[0] != "# gnome-about contributors - format 1":
  233.             return
  234.  
  235.         '''Filter the contributors list and append it'''
  236.         contributors = filter (validate_contributor, data)
  237.         map (self.append, contributors)
  238.  
  239.     def get (self):
  240.         '''Return a contributor from the currently randomized list. \
  241.            If we hit the end of the list, randomize it again'''
  242.         contributors_count = len (self)
  243.         if not contributors_count:
  244.             print "Warning: empty contributors list."
  245.             return
  246.         contributor = self[self.current]
  247.         self.current += 1
  248.         if self.current >= contributors_count:
  249.             '''Remove the last item of the list and reinsert it after \
  250.                shuffling to make sure it won't get displayed twice in a row'''
  251.             self.pop ()
  252.             random.shuffle (self)
  253.             index = random.randint (contributors_count / 2,
  254.                                     contributors_count)
  255.             self.insert (index, contributor)
  256.             self.current = 0
  257.         return contributor
  258.  
  259. # Animation is done using:
  260. #  * A custom gtk.Label, for catching mouse press events
  261. #  * A gtk.Alignment, for positionning and scrolling (the actual animation)
  262. #  * A gtk.Layout, so that the Alignment can be wider than what's displayed
  263. #    and thus let the Label appear and disappear smoothly
  264.  
  265. class AnimatedLabel (gtk.Layout):
  266.     '''Pretty animated label'''
  267.  
  268.     items   = None # items must either be a GettableList object
  269.                    # or expose a get () method'''
  270.     timeout = 0
  271.     format  = ""   # format must be a valid formatting string for a single %s
  272.  
  273.     item    = None
  274.     next    = None
  275.  
  276.     current = None
  277.     label   = None
  278.     source  = None
  279.     state   = 0    # 0 = appearing ; 1 = landed ; 2 = vanishing
  280.     pos     = 0.0
  281.  
  282.     width   = 0
  283.     height  = 0
  284.  
  285.     def __init__ (self, items, width, height, timeout, format = "%s"):
  286.         '''Initiate object'''
  287.         super (AnimatedLabel, self).__init__ ()
  288.         self.items = items
  289.         self.next = self.items.get () # Pop the first item
  290.         self.width = width
  291.         self.height = height
  292.         self.timeout = timeout
  293.         self.format = format
  294.         self.set_size_request (width, height)
  295.         self.connect ("button-press-event", self.on_button_press)
  296.         self.connect ("map", self.reset_animation)
  297.  
  298.     def reset_animation (self, *args):
  299.         '''Reset label and fire animation timer'''
  300.         self.reset_label ()
  301.         if not self.label:
  302.             return
  303.         self.source = gobject.timeout_add (5, self.animate)
  304.  
  305.     def make_label (self):
  306.         '''Build the label widgets'''
  307.         self.label = WindowedLabel ()
  308.         self.label.connect ("button-press-event", self.on_button_press)
  309.         self.label.set_justify (gtk.JUSTIFY_FILL)
  310.         self.label.set_line_wrap (True)
  311.         self.label.set_markup (self.format % self.item)
  312.         self.label.set_selectable (True)
  313.  
  314.     def reset_label (self):
  315.         '''Drop current label if any and create the new one'''
  316.         if self.current:
  317.             self.remove (self.current)
  318.             self.current = None
  319.             self.label = None
  320.         self.item = self.next
  321.         self.next = self.items.get ()
  322.         if not self.item:
  323.             return False
  324.         self.make_label ()
  325.         self.state = -1
  326.  
  327.     def on_button_press (self, widget, event):
  328.         '''Switch to next item upon left click'''
  329.         if event.button != 1 or not self.current:
  330.             return
  331.         # Remove the current timeout if any to avoid bad side effects
  332.         if self.source:
  333.             gobject.source_remove (self.source)
  334.         self.animate ()
  335.         return True
  336.  
  337. class VertAnimatedLabel (AnimatedLabel):
  338.     '''Vertically animated label'''
  339.  
  340.     rewind_text       = ""
  341.     last_label_height = 0
  342.  
  343.     def rewind_animate (self):
  344.         '''Animation function for the rewind step'''
  345.         self.source = None
  346.         if self.state == -2:
  347.             self.item = self.rewind_text
  348.             self.make_label ()
  349.             label_height = self.label.size_request ()[1]
  350.             total_height = self.height + label_height
  351.             self.pos = float (self.last_label_height) / total_height
  352.             self.current.set (0.5, self.pos, 0, 0)
  353.             self.state = 0
  354.             self.source = gobject.timeout_add (10, self.rewind_animate)
  355.         elif self.state == 0:
  356.             if self.pos < 1.0:
  357.                 '''Move towards the bottom position'''
  358.                 self.pos = min (1.0, self.pos + 0.01)
  359.                 self.current.set (0.5, self.pos, 0, 0)
  360.                 self.source = gobject.timeout_add (10, self.rewind_animate)
  361.             else:
  362.                 '''Bottommost position reached'''
  363.                 self.rewind_text = ""
  364.                 self.reset_animation ()
  365.         return False
  366.  
  367.     def animate (self):
  368.         '''The actual animation function'''
  369.         self.source = None
  370.         if self.state == -2:
  371.             self.rewind_animate ()
  372.         elif self.state == -1:
  373.             self.rewind_text += "\n\n%s" % self.item
  374.             self.state = 0
  375.             self.animate ()
  376.         elif self.state == 0:
  377.             if self.pos:
  378.                 '''Move towards the top position'''
  379.                 self.pos = max (0, self.pos - 0.02)
  380.                 label_height = self.label.size_request ()[1]
  381.                 total_height = self.height + label_height
  382.                 real_pos = float (self.pos * self.height + label_height) \
  383.                             / total_height
  384.                 self.current.set (0.5, real_pos, 0, 0)
  385.                 self.source = gobject.timeout_add (5, self.animate)
  386.             else:
  387.                 '''Topmost position reached'''
  388.                 self.state = 1
  389.                 self.pos = 1.0
  390.                 self.source = gobject.timeout_add (self.timeout, self.animate)
  391.         elif self.state == 1:
  392.             '''Dont let selected labels vanish until they are unselected'''
  393.             if self.label.get_selection_bounds () == ():
  394.                 self.state = 2
  395.             self.source = gobject.timeout_add (5, self.animate)
  396.         elif self.state == 2:
  397.             if not self.next:
  398.                 self.state = -2
  399.                 self.last_label_height = self.label.size_request ()[1]
  400.                 self.reset_animation ()
  401.                 self.source = gobject.timeout_add (1, self.animate)
  402.             elif self.pos:
  403.                 '''Move out of the visible region of the Layout'''
  404.                 self.pos = max (0, self.pos - 0.02)
  405.                 label_height = self.label.size_request ()[1]
  406.                 total_height = self.height + label_height
  407.                 real_pos = float (self.pos * label_height) \
  408.                             / total_height
  409.                 self.current.set (0.5, real_pos, 0, 0)
  410.                 self.source = gobject.timeout_add (5, self.animate)
  411.             else:
  412.                 '''Label has disappeared, bye bye'''
  413.                 self.reset_animation ()
  414.         return False
  415.  
  416.     def make_label (self):
  417.         '''Build a new label widget'''
  418.         super (VertAnimatedLabel, self).make_label ()
  419.         if not self.label:
  420.             return
  421.         self.label.set_size_request (self.width, -1)
  422.         self.current = gtk.Alignment (0.0, 1.0)
  423.         label_height = self.label.size_request ()[1]
  424.         height = self.size_request ()[1]
  425.         self.current.set_size_request (-1, 2 * label_height + height)
  426.         self.current.add (self.label)
  427.         self.put (self.current, 0, - label_height)
  428.         self.pos = 1.0
  429.         self.show_all ()
  430.  
  431. class HorzAnimatedLabel (AnimatedLabel):
  432.     '''Horizontally animated label'''
  433.  
  434.     def animate (self):
  435.         '''The actual animation function'''
  436.         self.source = None
  437.         if self.state == -2:
  438.             self.reset_animation ()
  439.         elif self.state <= 0:
  440.             if self.pos != 0.5:
  441.                 '''Move towards the center position'''
  442.                 self.pos = max (0.5, self.pos - 0.02)
  443.                 self.current.set (self.pos, 0.5, 0, 0)
  444.                 self.source = gobject.timeout_add (5, self.animate)
  445.             else:
  446.                 '''Center position reached, switch to return mode'''
  447.                 self.state = 1
  448.                 self.source = gobject.timeout_add (self.timeout, self.animate)
  449.         elif self.state == 1:
  450.             '''Dont let selected labels vanish until they are unselected'''
  451.             if self.label.get_selection_bounds () == ():
  452.                 self.state = 2
  453.             self.source = gobject.timeout_add (5, self.animate)
  454.         elif self.state == 2:
  455.             if self.pos:
  456.                 '''Disappear by moving left'''
  457.                 self.pos = max (0, self.pos - 0.02)
  458.                 self.current.set (self.pos, 0.5, 0, 0)
  459.                 self.source = gobject.timeout_add (5, self.animate)
  460.             else:
  461.                 '''Left position reached, let's move on'''
  462.                 self.reset_animation ()
  463.         return False
  464.  
  465.     def make_label (self):
  466.         '''Build a new label widget'''
  467.         super (HorzAnimatedLabel, self).make_label ()
  468.         if not self.label:
  469.             return
  470.         self.label.set_size_request (-1, self.height)
  471.         self.current = gtk.Alignment (1.0, 0.0)
  472.         label_width = self.label.size_request ()[0]
  473.         width = self.size_request ()[0]
  474.         self.current.set_size_request (2 * label_width + width, -1)
  475.         self.current.add (self.label)
  476.         self.put (self.current, - label_width, 0)
  477.         self.pos = 1.0
  478.         self.show_all ()
  479.  
  480. class WindowedLabel (gtk.Label):
  481.     '''Custom gtk.Label with an overlapping input-only gtk.gdk.Window'''
  482.  
  483.     event_window = None
  484.  
  485.     def __init__ (self, debug = False):
  486.         '''Initialize object and plug all signals'''
  487.         self.debug = debug
  488.         super (WindowedLabel, self).__init__ ()
  489.  
  490.     def do_realize (self):
  491.         '''Create a custom GDK window with which we will be able to play'''
  492.         gtk.Label.do_realize (self)
  493.         event_mask = self.get_events () | gtk.gdk.BUTTON_PRESS_MASK \
  494.                                         | gtk.gdk.BUTTON_RELEASE_MASK \
  495.                                         | gtk.gdk.KEY_PRESS_MASK
  496.         self.event_window = gtk.gdk.Window (parent = self.get_parent_window (),
  497.                                             window_type = gtk.gdk.WINDOW_CHILD,
  498.                                             wclass = gtk.gdk.INPUT_ONLY,
  499.                                             event_mask = event_mask,
  500.                                             x = self.allocation.x,
  501.                                             y = self.allocation.y,
  502.                                             width = self.allocation.width,
  503.                                             height = self.allocation.height)
  504.         self.event_window.set_user_data (self)
  505.  
  506.     def do_unrealize (self):
  507.         '''Destroy event window on unrealize'''
  508.         self.event_window.set_user_data (None)
  509.         self.event_window.destroy ()
  510.         gtk.Label.do_unrealize (self)
  511.  
  512.     def do_size_allocate (self, allocation):
  513.         '''Move & resize the event window to fit the Label's one'''
  514.         gtk.Label.do_size_allocate (self, allocation)
  515.         if self.flags () & gtk.REALIZED:
  516.             self.event_window.move_resize (allocation.x, allocation.y,
  517.                                            allocation.width, allocation.height)
  518.  
  519.     def do_map (self):
  520.         '''Show event window'''
  521.         gtk.Label.do_map (self)
  522.         self.event_window.show ()
  523.         '''Raise the event window to make sure it is over the Label's one'''
  524.         self.event_window.raise_ ()
  525.  
  526.     def do_unmap (self):
  527.         '''Hide event window on unmap'''
  528.         self.event_window.hide ()
  529.         gtk.Label.do_unmap (self)
  530.  
  531. gobject.type_register (WindowedLabel)
  532.  
  533. class HyperLink (WindowedLabel):
  534.     '''Clickable www link label'''
  535.  
  536.     url       = ""
  537.     menu      = None
  538.     selection = None
  539.  
  540.     def __init__ (self, label, url):
  541.         '''Initialize object'''
  542.         super (HyperLink, self).__init__ ()
  543.         markup = "<b><u>%s</u></b>" % label
  544.         self.set_markup (markup)
  545.         self.set_selectable (True)
  546.         self.url = url
  547.         self.create_menu ()
  548.         link_color = self.style_get_property ("link-color") 
  549.         if not link_color:
  550.             link_color = default_link_color
  551.         self.modify_fg (gtk.STATE_NORMAL, link_color)
  552.  
  553.     def open_url (self, *args):
  554.         '''Use GNOME API to open the url'''
  555.         try:
  556.             gnome.url_show (self.url)
  557.         except Exception, e:
  558.             print '''Warning: could not open "%s": %s''' % (self.url, e)
  559.  
  560.     def copy_url (self, *args):
  561.         '''Copy URL to Clipboard'''
  562.         clipboard = gtk.clipboard_get ("CLIPBOARD")
  563.         clipboard.set_text (self.url)
  564.  
  565.     def create_menu (self):
  566.         '''Create the popup menu that will be displayed upon right click'''
  567.         self.menu = gtk.Menu ()
  568.         open_item = gtk.ImageMenuItem (_("_Open URL"))
  569.         open_image = gtk.image_new_from_stock (gtk.STOCK_OPEN,
  570.                                                gtk.ICON_SIZE_MENU)
  571.         open_item.set_image (open_image)
  572.         open_item.connect ("activate", self.open_url)
  573.         open_item.show ()
  574.         self.menu.append (open_item)
  575.         copy_item = gtk.ImageMenuItem (_("_Copy URL"))
  576.         copy_image = gtk.image_new_from_stock (gtk.STOCK_COPY,
  577.                                                gtk.ICON_SIZE_MENU)
  578.         copy_item.set_image (copy_image)
  579.         copy_item.connect ("activate", self.copy_url)
  580.         copy_item.show ()
  581.         self.menu.append (copy_item)
  582.  
  583.     def display_menu (self, button, time, place = False):
  584.         '''Display utility popup menu'''
  585.         if place:
  586.             alloc = self.get_allocation ()
  587.             pos = self.event_window.get_origin ()
  588.             x = pos[0]
  589.             y = pos[1] + alloc.height
  590.             func = lambda *a: (x, y, True)
  591.         else:
  592.             func = None
  593.         self.menu.popup (None, None, func, button, time)
  594.  
  595.     def do_map (self):
  596.         '''Select the HAND2 cursor on map'''
  597.         WindowedLabel.do_map (self)
  598.         cursor = gtk.gdk.Cursor (gtk.gdk.HAND2)
  599.         self.event_window.set_cursor (cursor)
  600.  
  601.     def do_button_press_event (self, event):
  602.         '''Update selection bounds infos or display popup menu'''
  603.         if event.button == 1:
  604.             self.selection = self.get_selection_bounds ()
  605.         elif event.button == 3:
  606.             self.display_menu (event.button, event.time)
  607.             return True
  608.         WindowedLabel.do_button_press_event (self, event)
  609.  
  610.     def do_button_release_event (self, event):
  611.         '''Open url if selection hasn't changed since initial press'''
  612.         if event.button == 1:
  613.             selection = self.get_selection_bounds ()
  614.             if selection == self.selection:
  615.                 self.open_url ()
  616.                 return True
  617.         WindowedLabel.do_button_release_event (self, event)
  618.  
  619.     def do_key_press_event (self, event):
  620.         '''Open url when Return key is pressed'''
  621.         if event.keyval == gtk.keysyms.Return:
  622.             self.open_url ()
  623.             return True
  624.         elif event.keyval == gtk.keysyms.Menu \
  625.           or (event.keyval == gtk.keysyms.F10 \
  626.               and event.state & gtk.accelerator_get_default_mod_mask() == \
  627.                   gtk.gdk.SHIFT_MASK):
  628.             self.display_menu (event.keyval, event.time, place = True)
  629.             return True
  630.         WindowedLabel.do_key_press_event (self, event)
  631.  
  632. gobject.type_register (HyperLink)
  633.  
  634. class GnomeLogo (gtk.Widget):
  635.     '''Simple widget displaying a colored GNOME logo'''
  636.  
  637.     _surface = None
  638.     _parent  = None
  639.     _file    = None
  640.  
  641.     def __init__ (self, parent, file):
  642.         '''Initialize object and do the initial painting'''
  643.         gtk.Widget.__init__ (self)
  644.  
  645.         self._parent = parent
  646.         self._file = file
  647.  
  648.         self.paint_surface ()
  649.  
  650.         width = self._surface.get_width ()
  651.         height = self._surface.get_height ()
  652.         self.set_size_request (width, height)
  653.  
  654.         self.set_flags(self.flags() | gtk.NO_WINDOW)
  655.  
  656.     def paint_surface (self):
  657.         '''Paint image and color overlay'''
  658.         self._surface = cairo.ImageSurface.create_from_png (self._file)
  659.  
  660.         text_color = self._parent.get_style ().fg[gtk.STATE_NORMAL]
  661.  
  662.         cr = cairo.Context (self._surface)
  663.         cr.set_source_rgb (float (text_color.red) / 65535,
  664.                            float (text_color.green) / 65535,
  665.                            float (text_color.blue) / 65535)
  666.         cr.set_operator (cairo.OPERATOR_ATOP)
  667.         cr.paint ()
  668.  
  669.     def do_expose_event (self, event):
  670.         '''Paint the saved surface to the widget context'''
  671.         cr = self.window.cairo_create ()
  672.  
  673.         cr.set_operator (cairo.OPERATOR_OVER)
  674.         cr.set_source_surface (self._surface, 10, 10)
  675.         cr.paint ()
  676.  
  677.     def do_style_set (self, *args):
  678.         '''Style changed, let's repaint image'''
  679.         self.paint_surface ()
  680.         self.queue_draw ()
  681.  
  682. gobject.type_register (GnomeLogo)
  683.  
  684. class GnomeAboutHeader (gtk.Layout):
  685.     '''Pretty header for gnome-about'''
  686.  
  687.     program = None
  688.     links   = []
  689.  
  690.     width   = 0
  691.     height  = 0
  692.  
  693.     def __init__ (self, program, links):
  694.         '''Initialize object, plug map signal'''
  695.         super (GnomeAboutHeader, self).__init__ () 
  696.         self.program = program
  697.         self.links = links
  698.  
  699.     def do_realize (self):
  700.         '''Load header and build links'''
  701.         gtk.Layout.do_realize (self)
  702.  
  703.         current_x = 0
  704.         current_y = 0
  705.         base_y = 0
  706.  
  707.         header = self.load_header ()
  708.         if header:
  709.             self.put (header, 0, 0)
  710.             current_y = header.get_pixbuf ().get_height ()
  711.             base_y = current_y + 4
  712.             line = self.create_line ()
  713.             image = gtk.Image ()
  714.             image.set_from_pixmap (line, None)
  715.             self.put (image, 0, current_y)
  716.  
  717.         logo = self.load_logo ()
  718.         if logo:
  719.             self.put (logo, 0, 0)
  720.             logo_size = logo.get_size_request ()
  721.             current_x += logo_size[0] + 25
  722.             current_y = logo_size[1] + 20
  723.  
  724.         dot = self.create_dot ()
  725.  
  726.         def make_link_widget (link):
  727.             '''Helper function which makes an HyperLink and shows it'''
  728.             label = HyperLink (link[0], link[1])
  729.             label.show_all ()
  730.             return label
  731.  
  732.         widgets = map (make_link_widget, self.links)
  733.         put_widgets = 0
  734.         for widget in widgets:
  735.             if put_widgets > 0:
  736.                 if dot:
  737.                     image = gtk.Image ()
  738.                     image.set_from_pixmap (dot, None)
  739.                     self.put (image, current_x + 5, base_y + 6)
  740.                 current_x += 16
  741.             self.put (widget, current_x, base_y)
  742.             current_x += widget.size_request ()[0]
  743.             put_widgets += 1
  744.  
  745.         self.width = current_x + 10
  746.         self.height = current_y
  747.         self.set_size_request (self.width, self.height)
  748.         self.show_all ()
  749.  
  750.     def load_header (self):
  751.         '''Load a random header image as a gtk.Image'''
  752.  
  753.         directory = locate_file (self.program, "headers")
  754.         if not directory:
  755.             print "Warning: header images directory not found."
  756.             return None
  757.  
  758.         try:
  759.             headers = os.listdir (directory)
  760.         except:
  761.             print "Warning: failed to read header images directory."
  762.             return None
  763.  
  764.         headers = filter (lambda s: s[-4:] in (".png", ".gif"), headers)
  765.         header = random.choice (headers)
  766.  
  767.         file = os.path.join (directory, header)
  768.         try:
  769.             pixbuf = gtk.gdk.pixbuf_new_from_file (file)
  770.         except:
  771.             print '''Warning: failed to load header image "%s".''' % file
  772.             return None
  773.  
  774.         image = gtk.Image ()
  775.         image.set_from_pixbuf (pixbuf)
  776.  
  777.         return image
  778.  
  779.     def load_logo (self):
  780.         '''Load a GNOME foot logo as a gtk.Image'''
  781.  
  782.         file = locate_file (self.program, LOGO_FILE)
  783.         if not file:
  784.             print '''Warning: GNOME logo file "%s" not found.''' % LOGO_FILE
  785.             return None
  786.  
  787.         try:
  788.             logo = GnomeLogo (self, file)
  789.         except Exception:
  790.             print '''Warning: failed to load GNOME logo image "%s".''' % file
  791.             return None
  792.  
  793.         return logo
  794.  
  795.     def create_dot (self):
  796.         '''Create a pixmap containing a simple dot'''
  797.         pixmap = gtk.gdk.Pixmap (self.window, 6, 6)
  798.         context = pixmap.cairo_create ()
  799.         context.set_operator (cairo.OPERATOR_SOURCE)
  800.         context.set_source_color (self.style.bg[self.state])
  801.         context.paint ()
  802.         context.set_operator (cairo.OPERATOR_OVER)
  803.         context.set_source_color (self.style.fg[self.state])
  804.         context.arc (3, 3, 2.3, 0, 2 * pi)
  805.         context.fill ()
  806.         return pixmap
  807.  
  808.     def create_line (self, width = 2000, height = 1):
  809.         '''Create a pixmap containing a simple line'''
  810.         pixmap = gtk.gdk.Pixmap (self.window, width, height)
  811.         context = pixmap.cairo_create ()
  812.         context.set_operator (cairo.OPERATOR_SOURCE)
  813.         context.set_source_rgb (0, 0, 0)
  814.         context.paint ()
  815.         return pixmap
  816.  
  817. gobject.type_register (GnomeAboutHeader)
  818.  
  819. class GnomeAbout (gtk.Dialog):
  820.     '''Super pretty About Dialog for the GNOME Desktop'''
  821.  
  822.     program              = None
  823.     header               = None
  824.     contributors         = None
  825.     description_messages = GettableList ()
  826.     system_infos         = []
  827.  
  828.     def __init__ (self, ui = True):
  829.         '''Initialize underlying gnome.Program, Contributors list, UI...'''
  830.         super (GnomeAbout, self).__init__ (_("About the GNOME Desktop"),
  831.                                            buttons = (gtk.STOCK_CLOSE,
  832.                                                       gtk.RESPONSE_CLOSE))
  833.  
  834.         defs = {gnome.PARAM_APP_DATADIR : DATADIR}
  835.         self.program = gnome.program_init ("gnome-about", VERSION,
  836.                                            properties = defs)
  837.         # Immediately fetch system infos to load description messages
  838.         self.system_infos = self.get_system_infos ()
  839.  
  840.         if not ui:
  841.             return
  842.  
  843.         self.contributors = GnomeContributors (self.program)
  844.  
  845.         icon_file = ICONDIR + "/gnome-logo-icon-transparent.png"
  846.         try:
  847.             self.set_icon_from_file (icon_file)
  848.         except gobject.GError:
  849.             pass
  850.  
  851.         self.create_ui ()
  852.  
  853.         self.set_default_response (gtk.RESPONSE_CLOSE)
  854.         self.set_position (gtk.WIN_POS_CENTER)
  855.         map (lambda prop: self.set_property (prop[0], prop[1]),
  856.              [("allow-grow", False), ("allow-shrink", False)])
  857.         self.connect ("delete-event", gtk.main_quit)
  858.         self.connect ("response", self.response_callback)
  859.  
  860.     def gnome_version (self):
  861.         '''Output basic GNOME version information to console'''
  862.         def print_info (info):
  863.             infos_dict = {"name": info[0], "value": info[1]}
  864.             # Translators: %(name)s and %(value)s should not be translated:
  865.             # it's a way to identify a string, so just handle them like %s
  866.             print _("%(name)s: %(value)s") % infos_dict
  867.         map (print_info, self.system_infos)
  868.  
  869.     def create_ui (self):
  870.         '''Fill our Dialog with some lovely widgets'''
  871.         self.set_has_separator (False)
  872.         main_box = self.get_children ()[0] # Get the internal Dialog VBox
  873.  
  874.         '''Pretty header'''
  875.         self.header = GnomeAboutHeader (self.program, header_links)
  876.         main_box.pack_start (self.header)
  877.  
  878.         welcome_label = WindowedLabel ()
  879.         welcome_label.set_markup ("<big><big><b>%s</b></big></big>" % \
  880.                                   _("Welcome to the GNOME Desktop"))
  881.         main_box.pack_start (welcome_label)
  882.  
  883.         descriptions_label = VertAnimatedLabel (self.description_messages,
  884.                                                 300, 120,
  885.                                                 DESCRIPTION_DELAY, "%s")
  886.         welcome_label.connect ("button-press-event",
  887.                                descriptions_label.on_button_press)
  888.         box = gtk.EventBox ()
  889.         alignment = gtk.Alignment (0.5, 0.5)
  890.         alignment.set_padding (10, 10, 0, 0)
  891.         alignment.add (descriptions_label)
  892.         box.connect ("button-press-event", descriptions_label.on_button_press)
  893.         box.add (alignment)
  894.         main_box.pack_start (box)
  895.  
  896.         by_label = WindowedLabel (True)
  897.         by_label.set_markup ("<big><b>%s</b></big>" % _("Brought to you by:"))
  898.         main_box.pack_start (by_label)
  899.  
  900.         alignment = gtk.Alignment (0.5, 0.5)
  901.         '''Realize the header right now to get everything (especially the
  902. contributors list) correctly positionned and sized.'''
  903.         self.header.realize ()
  904.         width = self.header.width
  905.         label = HorzAnimatedLabel (self.contributors, width,
  906.                                    30, CONTRIBUTOR_DELAY, "<b>%s</b>")
  907.         by_label.connect ("button-press-event", label.on_button_press)
  908.         label.show_all ()
  909.         alignment.add (label)
  910.  
  911.         main_box.pack_start (alignment)
  912.  
  913.         '''System info labels'''
  914.         def make_info_label (info):
  915.             if not info[1]:
  916.                 return False
  917.             label = gtk.Label ()
  918.             infos_dict = {"name": info[0], "value": info[1]}
  919.             # Translators: %(name)s and %(value)s should not be translated:
  920.             # it's a way to identify a string, so just handle them like %s
  921.             label.set_markup (_("<b>%(name)s:</b> %(value)s") % infos_dict)
  922.             label.set_selectable (True)
  923.             label.connect ("focus-out-event",
  924.                            lambda l, e: l.select_region (-1, -1))
  925.             alignment = gtk.Alignment (0, 0.5)
  926.             alignment.set_padding (0, 4, 8, 0)
  927.             alignment.add (label)
  928.             return alignment
  929.  
  930.         info_labels = map (make_info_label, self.system_infos)
  931.         info_labels = filter (lambda l: l <> False, info_labels)
  932.         map (lambda l: main_box.pack_start (l, False, False), info_labels)
  933.  
  934.         main_box.show_all ()
  935.  
  936.     def get_system_infos (self):
  937.         '''Fetch various system infos'''
  938.         file = locate_file (self.program, "gnome-version.xml")
  939.         if not file:
  940.             print '''Warning: "gnome-version.xml" file not found.'''
  941.             return []
  942.  
  943.         f = open (file, "r")
  944.         try:
  945.             document = xml.dom.minidom.parse (f)
  946.         finally:
  947.             f.close ()
  948.  
  949.         if document.firstChild.nodeName != "gnome-version":
  950.             print '''Warning: corrupted "gnome-version.xml".'''
  951.             return []
  952.  
  953.         infos = {
  954.                     "platform" : "",
  955.                     "minor" : "",
  956.                     "micro" : "",
  957.                     "distributor" : "",
  958.                     "date" : ""
  959.                 }
  960.  
  961.         for node in document.firstChild.childNodes:
  962.             if node.nodeName in infos:
  963.                 infos[node.nodeName] = node.firstChild.nodeValue
  964.             elif node.nodeName == "description":
  965.                 self.load_description_messages (node)
  966.  
  967.         '''Format version'''
  968.         if not len (infos["platform"]):
  969.             version = ""
  970.         elif not len (infos["minor"]):
  971.             version = infos["platform"]
  972.         elif not len (infos["micro"]):
  973.             version = "%s.%s" % (infos["platform"], infos["minor"])
  974.         else:
  975.             version = "%s.%s.%s" % (infos["platform"], infos["minor"],
  976.                                     infos["micro"])
  977.  
  978.         date = cleanup_date (infos["date"])
  979.  
  980.         retval = []
  981.         if version:
  982.             retval.append((_("Version"), version))
  983.         if infos["distributor"]:
  984.             retval.append((_("Distributor"), infos["distributor"]))
  985.         if date:
  986.             retval.append((_("Build Date"), date))
  987.  
  988.         return retval
  989.  
  990.     def load_description_messages (self, node):
  991.         '''Find the best translation of each description message'''
  992.         languages = get_language_names () + [""]
  993.         def desc_filter_func (node):
  994.             '''Helper filter function for XML descriptions'''
  995.             return node.nodeName == "p" \
  996.                and node.getAttribute ("xml:lang") in languages
  997.         def desc_sort_func (a, b):
  998.             '''Helper sorting function to sort translated messages'''
  999.             return cmp (languages.index (a), languages.index (b))
  1000.         descs = filter (desc_filter_func, node.childNodes)
  1001.         raw_descs = filter (lambda n: n.getAttribute ("xml:lang") == "", descs)
  1002.         i = -1
  1003.         translations = []
  1004.         for desc in raw_descs:
  1005.             new_i = descs.index (desc)
  1006.             if i != - 1:
  1007.                 # Append the previous description
  1008.                 translations.append (descs[i:new_i])
  1009.             i = new_i
  1010.         # Open ended to retrieve all translation for last description
  1011.         translations.append (descs[i:])
  1012.         messages = GettableList ()
  1013.         for block in translations:
  1014.             sorted_descs = sorted (block, cmp = desc_sort_func,
  1015.                                    key = lambda n: n.getAttribute ("xml:lang"))
  1016.             best = sorted_descs[0].firstChild.nodeValue
  1017.             messages.append (best)
  1018.         self.description_messages = messages
  1019.  
  1020.     def response_callback (self, widget, response):
  1021.         '''Handle dialog response when Close button is triggered'''
  1022.         if response == gtk.RESPONSE_CLOSE:
  1023.             gtk.main_quit ()
  1024.  
  1025. if __name__ == "__main__":
  1026.     parser = OptionParser (
  1027.             option_list = [
  1028.                     make_option ("--gnome-version",
  1029.                                  action="store_true",
  1030.                                  dest="gnome_version",
  1031.                                  help=_("Display information on this GNOME version")),
  1032.             # FIXME: remove this when we can get all the default
  1033.             # options
  1034.                     make_option ("--version",
  1035.                                  action="store_true",
  1036.                                  dest="version",
  1037.                                  help=VERSION),
  1038.                 ])
  1039.     #FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=496278
  1040.     parser.parse_args (sys.argv)
  1041.     if parser.values.gnome_version:
  1042.         about = GnomeAbout (ui = False)
  1043.         about.gnome_version ()
  1044.     elif parser.values.version:
  1045.         print "GNOME gnome-about %s" % (VERSION)
  1046.     else:
  1047.         about = GnomeAbout ()
  1048.         about.show_all ()
  1049.         try:
  1050.             gtk.main ()
  1051.         except KeyboardInterrupt:
  1052.             pass
  1053.